﻿using Engine;
using Engine.FileFormats3D;
using Engine.Graphics;
using Engine.Graphics.VertexStructures;
using Engine.Shape;
using SharpDX;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;

namespace ModelViewer.TabsForModelviewer
{
    public partial class ModelExtractorControl : UserControl, IRenderableObject
    {
        #region Private Fields

        private ActionModeEnum actionMode;
        private Engine.MeshObject.BoundingBox boundingBox;
        private Engine.Engine engine;
        private ModelViewerForm modelViewer;
        private Wr2Scenario wr2Scenario;

        #endregion Private Fields

        #region Public Constructors

        public ModelExtractorControl()
        {
            InitializeComponent();
            actionMode = ActionModeEnum.NewNode;
        }

        #endregion Public Constructors

        #region Public Enums

        public enum ActionModeEnum
        {
            NewNode,
            DeleteNode,
            MoveNode
        }

        #endregion Public Enums

        #region Public Methods

        public void Initialize(ModelViewerForm modelViewer)
        {
            this.modelViewer = modelViewer;
            this.engine = modelViewer.EngineComplete;
            this.boundingBox = new Engine.MeshObject.BoundingBox(this.modelViewer.EngineComplete, new Vector3(-10, 0, 10), new Vector3(10, 10, -10));
            this.modelViewer.model.NewShape += this.shapeModifer1.OnNewShape;
            this.shapeModifer1.InitializeEngine(this.modelViewer.EngineComplete, modelViewer);
            this.engine.Grafic.AddRenderableObjects(this, 10);
        }

        public void Render(int renderStep)
        {
            this.engine.Grafic.ShaderManager[0].SetShader(this.engine.Grafic.ShaderManager[0].ColorShader);
            this.engine.Grafic.SetFillmodeSolid();
            this.engine.Grafic.BlendStateEnableAlpha(false);
            this.engine.Grafic.DepthStateEnableZ(false);
            if (this.radioButton_CreateBoundingBox.Checked)
                this.boundingBox.Render(80);
        }

        public void SetWR2Scenery(object wr2Scenery, EventArgs e)
        {
            this.wr2Scenario = wr2Scenario as Wr2Scenario;
        }

        public void UseThisTool(Vector3 positionIn3dSpace, bool isPositionFromTerrain, bool mouseInRenderwindow, NumericUpDown zHeight, Panel renderWindow)
        {
            // Camera movement.
            if (mouseInRenderwindow)
                MoveCamera(isPositionFromTerrain);

            if (mouseInRenderwindow)
            {
                // Create Boundingbox
                if (this.radioButton_CreateBoundingBox.Checked && this.modelViewer.Wr2Scenario != null)
                {
                    CreateBoundingBox();
                }

                // Deleting selected polygons
                if (radioButton_Polygon.Checked && this.modelViewer.EngineComplete.Mouse.ButtonL)
                {
                    this.modelViewer.model.RenderMode = EnumRenderMode.Mesh;
                    DeleteSelectedPolygons();
                }

                // Deleting selected material
                if (this.radioButton_DeleteMaterial.Checked)
                {
                    this.modelViewer.model.RenderMode = EnumRenderMode.MeshWithWireframe;

                    if (this.modelViewer.EngineComplete.Mouse.ButtonL)
                        DeleteSelectedMaterial();

                    // render only material subsets to which the mouse points.
                    Vector3 positionOnModel;
                    int materialID, polygonID;

                    if (this.modelViewer.model.ScenerieObject.Mesh.Intersect(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, out positionOnModel, out materialID, out polygonID))
                    {
                        this.modelViewer.model.SelectedSubsets = new List<int>() { materialID };
                        //this.modelViewer.EngineComplete.Device.SetRenderState(SharpDX.Direct3D9.RenderState.CullMode, SharpDX.Direct3D9.Cull.None);
                    }
                    else
                    {
                        //   this.modelViewer.EngineComplete.Device.SetRenderState(SharpDX.Direct3D9.RenderState.CullMode, SharpDX.Direct3D9.Cull.Counterclockwise);
                    }
                    if (this.modelViewer.EngineComplete.Mouse.ButtonR)
                        this.modelViewer.model.SelectedSubsets = new List<int>(this.modelViewer.model.ScenerieObject.Mesh.materialsIDs);
                }

                // Rotate Object
                if (this.radioButton_Rotate.Checked && this.modelViewer.EngineComplete.Mouse.ButtonLeftPressingStarted)
                {
                    this.modelViewer.model.RenderMode = EnumRenderMode.Mesh;
                    RotateModel();
                }
            }
        }

        #endregion Public Methods

        #region Internal Methods

        /// <summary>
        /// Gets the mouse-position on terrain
        /// </summary>
        /// <param name="vNear">Near plane (camera Position)</param>
        /// <param name="vFar">far plane</param>
        /// <param name="viewDistance">only Sectors which are visible are used for calculate</param>
        /// <returns>Return the Posion where the mouse cursor cuttet the Terrain</returns>
        internal bool GetPositionOnTerrain(Vector3 vNear, Vector3 vFar, Meshobject[] meshes, out Vector3 nearestIntersectedPosition)
        {
            nearestIntersectedPosition = Vector3.Zero;

            //Get the List of sectors which are under Mouse cursor
            List<Vector3> intersectPositions = new List<Vector3>();

            for (int i = 0; i < meshes.Length; i++)
            {
                Vector3 position;

                if (meshes[i].Intersect(vNear, vFar, out position))
                {
                    intersectPositions.Add(position);
                }
            }

            float smallestDistance = float.MaxValue;
            for (int i = 0; i < intersectPositions.Count; i++)
            {
                float currentDistance;
                if ((currentDistance = GeoMath.DistanceBtw2Points(vNear, intersectPositions[i])) < smallestDistance)
                {
                    smallestDistance = currentDistance;
                    nearestIntersectedPosition = intersectPositions[i];
                }
            }

            if (intersectPositions.Count > 0)
                return true;
            return false;
        }

        #endregion Internal Methods

        #region Private Methods

        private static void AddVertexToMesh(ref Mesh3dData meshobject, ref int startIndexForIndices, MyOwnVertex.MyPositionNormalTextured vertex)
        {
            // Now we must find the vertex. if it isn´t
            bool verexFound = false;
            for (int m = 0; m < meshobject.Vertices.Count; m++)
            {
                if (meshobject.Vertices[m] == vertex)
                {
                    meshobject.Indices.Insert(startIndexForIndices, (ushort)m);
                    startIndexForIndices++;
                    verexFound = true;
                    return;
                }
            }

            if (!verexFound)
            {
                meshobject.Indices.Insert(startIndexForIndices, (ushort)meshobject.Vertices.Count);
                meshobject.Vertices.Add(vertex);
                startIndexForIndices++;
            }
        }

        private void button_CenterAll_Click(object sender, EventArgs e)
        {
            this.modelViewer.model.ScenerieObject.CenterX();
            this.modelViewer.model.ScenerieObject.CenterZ();

            this.modelViewer.model.ScenerieObject.GroundY();
        }

        private void button_CenterX_Click(object sender, EventArgs e)
        {
            this.modelViewer.model.ScenerieObject.CenterX();
        }

        private void button_CreateShape_Click(object sender, EventArgs e)
        {
            Shapes shape = Shapes.ConvertFromMesh(this.modelViewer.model.ScenerieObject.Mesh);
            this.modelViewer.model.SetShape(shape);
        }

        private void button_Save_Click(object sender, EventArgs e)
        {
            string filename = Application.StartupPath + "\\Temp.WrMesh";
            Vector2 upperLeft = new Vector2(Math.Min(this.boundingBox.UpperLeft.X, this.boundingBox.LowerRight.X), Math.Max(this.boundingBox.UpperLeft.Z, this.boundingBox.LowerRight.Z));
            Vector2 lowerRight = new Vector2(Math.Max(this.boundingBox.UpperLeft.X, this.boundingBox.LowerRight.X), Math.Min(this.boundingBox.UpperLeft.Z, this.boundingBox.LowerRight.Z));

            ExtractModelFronmScenario(this.modelViewer.Wr2Scenario.Meshes, upperLeft, lowerRight, filename);

            Process.Start("ModelViewer.exe", filename);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.modelViewer.model.ScenerieObject.CenterY();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.modelViewer.model.ScenerieObject.CenterZ();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            this.modelViewer.model.ScenerieObject.GroundY();
        }

        private void CreateBoundingBox()
        {
            if (this.modelViewer.EngineComplete.Mouse.ButtonLeftPressingStarted)
            {
                Vector3 positionOnScenery;
                if (GetPositionOnTerrain(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, this.modelViewer.Wr2Scenario.MeshBlocks, out positionOnScenery))
                {
                    this.boundingBox.UpperLeft = positionOnScenery;
                }
            }

            if (this.modelViewer.EngineComplete.Mouse.ButtonL)
            {
                Vector3 positionOnScenery;
                if (GetPositionOnTerrain(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, this.modelViewer.Wr2Scenario.MeshBlocks, out positionOnScenery))
                {
                    this.boundingBox.LowerRight = positionOnScenery;
                }
            }
        }

        private void DeleteSelectedMaterial()
        {
            Vector3 positionOnModel;
            int materialID, polygonID;

            if (this.modelViewer.model.ScenerieObject.Mesh.Intersect(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, out positionOnModel, out materialID, out polygonID))
            {
                Mesh3dData meshData = this.modelViewer.model.ScenerieObject.MeshData;
                int numberOfPolygons = 0;
                for (int i = 0; i < meshData.PolyTextures.Count; i++)
                {
                    if (materialID == i)
                    {
                        meshData.Indices.RemoveRange(numberOfPolygons * 3, meshData.PolyTextures[i] * 3);

                        meshData.PolyTextures.RemoveAt(i);
                        meshData.Materials.RemoveAt(i);
                        this.modelViewer.model.ScenerieObject.Mesh.MaterialManager.RemoveMaterialAt(i);
                        i--;

                        break;
                    }
                    numberOfPolygons += meshData.PolyTextures[i];
                }

                meshData.RemoveUnusedVertices();
                // Create the new Mesh
                this.modelViewer.model.ScenerieObject.Mesh = new Meshobject(this.modelViewer.EngineComplete.Device, this.modelViewer.MaterialManager, this.modelViewer.ShaderManager, meshData);
                this.modelViewer.model.SelectedSubsets.Clear();
            }
        }

        private void DeleteSelectedPolygons()
        {
            Vector3 positionOnModel;
            int materialID, polygonID;

            if (this.modelViewer.model.ScenerieObject.Mesh.Intersect(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, out positionOnModel, this.checkBox_BothSides.Checked, out materialID, out polygonID))
            {
                Mesh3dData meshData = this.modelViewer.model.ScenerieObject.MeshData;
                meshData.Indices.RemoveRange(polygonID * 3, 3);
                int numberOfPolygons = 0;
                for (int i = 0; i < meshData.PolyTextures.Count; i++)
                {
                    numberOfPolygons += meshData.PolyTextures[i];
                    if (numberOfPolygons > polygonID)
                    {
                        meshData.PolyTextures[i]--;
                        if (meshData.PolyTextures[i] == 0) // Whenn there are 0 polygons for the material, both entries will be deleted.
                        {
                            meshData.PolyTextures.RemoveAt(i);
                            meshData.Materials.RemoveAt(i);
                            i--;
                        }
                        break;
                    }
                }
                meshData.RemoveUnusedVertices();
                // Create the new Mesh
                this.modelViewer.model.ScenerieObject.Mesh = new Meshobject(this.modelViewer.EngineComplete.Device, this.modelViewer.MaterialManager, this.modelViewer.ShaderManager, meshData);
            }
        }

        private void ExtractModelFronmScenario(Mesh3dData[] meshobjects, Vector2 upperLeft, Vector2 lowerRight, string filenameForNewModel)
        {
            Mesh3dData meshobject = new Mesh3dData(new List<Material>());

            // For all meshblocks of scenery.
            for (int i = 0; i < meshobjects.Length; i++)
            {
                int currentPolygonId = 0;
                // for each material
                for (int j = 0; j < meshobjects[i].Materials.Count; j++)
                {
                    // for each polzgon which belongs to the material
                    for (int k = 0; k < meshobjects[i].PolyTextures[j]; k++, currentPolygonId++)
                    {
                        // If thw first vertex of the polygon is fititng the boundaries it must add to the new mesh.
                        if (meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3]].X > upperLeft.X &&
                            meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3]].X < lowerRight.X &&
                            meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3]].Z < upperLeft.Y &&
                            meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3]].Z > lowerRight.Y)
                        {
                            // Some help varibles for insering
                            int startIndexForIndices = 0;
                            bool materialFound = false;

                            // First we must find the material id in the new mesh
                            for (int l = 0; l < meshobject.Materials.Count; l++)
                            {
                                startIndexForIndices += (meshobject.PolyTextures[l] * 3);

                                if (meshobject.Materials[l] == meshobjects[i].Materials[j])
                                {
                                    meshobject.PolyTextures[l]++;
                                    materialFound = true;

                                    MyOwnVertex.MyPositionNormalTextured vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3 + 0]];
                                    AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                                    vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3 + 1]];
                                    AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                                    vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3 + 2]];
                                    AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                                }
                            }

                            if (!materialFound)
                            {
                                // Add Material and increase the nu8mber of polygons for that material.
                                meshobject.Materials.Add(meshobjects[i].Materials[j]);
                                meshobject.PolyTextures.Add(1);

                                startIndexForIndices = meshobject.Indices.Count;
                                MyOwnVertex.MyPositionNormalTextured vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3]];
                                AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                                vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3 + 1]];
                                AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                                vertex = meshobjects[i].Vertices[meshobjects[i].Indices[currentPolygonId * 3 + 2]];
                                AddVertexToMesh(ref meshobject, ref startIndexForIndices, vertex);
                            }
                        }
                    }
                }
            }

            Meshobject mesh = new Meshobject(this.modelViewer.EngineComplete.Device, this.modelViewer.MaterialManager, this.modelViewer.ShaderManager, meshobject);

            SceneryObject sceneObject = new SceneryObject(this.modelViewer.EngineComplete);
            sceneObject.Mesh = mesh;

            sceneObject.CenterX();
            sceneObject.CenterZ();
            sceneObject.GroundY();

            sceneObject.Save(filenameForNewModel, this.modelViewer.MaterialManager);
        }

        private void MoveCamera(bool getHeightFromTerrain)
        {
            var mouse = this.modelViewer.EngineComplete.Mouse;
            var camera = this.modelViewer.EngineComplete.Camera;
            NumericUpDown zHeight = new NumericUpDown();

            // Default movings.
            if (mouse.ButtonM)// If middle Mouse-Button is pressed
            {
                camera.RotateTargetAroundCamera(-mouse.AxisX, -mouse.AxisY);
            }
            if (mouse.AxisZ != 0)// ZOOM in  and out
            {
                camera.MoveForward(mouse.AxisZ);
            }

            switch (this.actionMode)
            {
                case ActionModeEnum.NewNode:
                    if (mouse.ButtonRreleased & !mouse.HasMovedWhilePressingR)
                    {
                    }
                    else if (mouse.ButtonR)
                    {
                        if (getHeightFromTerrain)
                        {
                            camera.MoveUpDown(mouse.AxisY);
                            camera.MoveXY(-mouse.AxisX, 0.0f);
                        }
                        else
                        {
                            zHeight.Value += (decimal)-mouse.AxisY;
                        }
                    }
                    break;

                case ActionModeEnum.MoveNode:
                    if (mouse.ButtonRreleased & !mouse.HasMovedWhilePressingR)
                    {
                    }
                    else if (mouse.ButtonR || mouse.ButtonRreleased)
                    {
                        if (getHeightFromTerrain)
                        {
                            if (mouse.ButtonR)// If right Mouse-Button is pressed
                            {
                                camera.MoveUpDown(mouse.AxisY);
                                camera.MoveXY(-mouse.AxisX, 0.0f);
                            }
                        }
                        else if (!getHeightFromTerrain)
                        {
                            if (mouse.ButtonR)// If right Mouse-Button is pressed
                            {
                                camera.MoveUpDown(mouse.AxisY);
                                camera.MoveXY(-mouse.AxisX, 0.0f);
                            }
                        }
                    }

                    break;

                case ActionModeEnum.DeleteNode:
                    break;
            }
        }

        private void RotateModel()
        {
            Vector3 positionOnModel;
            int materialID, polygonID;

            if (this.modelViewer.model.ScenerieObject.Mesh.Intersect(this.modelViewer.EngineComplete.Grafic.VNear, this.modelViewer.EngineComplete.Grafic.VFar, out positionOnModel, out materialID, out polygonID))
            {
                // Get normal of selected polygon
                Vector3 normal = GeoMath.Normal2Poly(
                    this.modelViewer.model.ScenerieObject.Mesh.Vertices[this.modelViewer.model.ScenerieObject.Mesh.Indices[polygonID * 3]].Position,
                    this.modelViewer.model.ScenerieObject.Mesh.Vertices[this.modelViewer.model.ScenerieObject.Mesh.Indices[polygonID * 3 + 1]].Position,
                    this.modelViewer.model.ScenerieObject.Mesh.Vertices[this.modelViewer.model.ScenerieObject.Mesh.Indices[polygonID * 3 + 2]].Position
                    );

                Matrix rotation = Matrix.RotationY((float)Math.Atan2(normal.X, -normal.Z));

                this.modelViewer.model.ScenerieObject.MeshData.Rotation(rotation);

                // Create the new Mesh
                this.modelViewer.model.ScenerieObject.Mesh = new Meshobject(this.modelViewer.EngineComplete.Device, this.modelViewer.MaterialManager, this.modelViewer.ShaderManager, this.modelViewer.model.ScenerieObject.MeshData);
            }
        }

        #endregion Private Methods
    }
}